package com.file.parser;

import com.file.bo.ResponseData;
import com.file.bo.mail.HXB;
import com.file.bo.mail.HXBTran;
import com.file.constant.ErrorCode;
import com.file.util.JsonUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition;
import technology.tabula.*;
import technology.tabula.detectors.NurminenDetectionAlgorithm;
import technology.tabula.detectors.SpreadsheetDetectionAlgorithm;
import technology.tabula.extractors.BasicExtractionAlgorithm;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
public class HXBPdfParser extends BaseDecryptPdfParser {

    private static final int MERGE_THRESHOLD = 10;

    public ResponseData<String> parseHXBPdfToJson(String daId, String filePath, String pdfPassword) {
        log.info("parseHXBPdfToJson started, daId:{}", daId);
        String json = null;

        try {
            HXB hxb = parseHXBPdf(filePath, pdfPassword);
            json = JsonUtils.convertObjectToJson(hxb);
        } catch (Exception e) {
            log.error("OnError|{}||{}|{}|{}|{}", ErrorCode.FILE_PARSE_EXCEPTION.getCode(), daId, "", "", "parseHXBPdfToJson failed", e);
            return new ResponseData<String>(json, ErrorCode.FILE_PARSE_EXCEPTION.getCode(),
                    ErrorCode.FILE_PARSE_EXCEPTION.getMsg());
        }

        log.info("parseHXBPdfToJson completed, daId:{}", daId);
        return new ResponseData<String>(json, ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg());
    }

    private HXB parseHXBPdf(String filePath, String pdfPassword) {
        String pdfHeaderText = parsePdfHeaderText(filePath, pdfPassword);
        HXB hxb = parseHXBHeader(pdfHeaderText);
        boolean hasCounterPartyName = false;
        if (pdfHeaderText.contains("对方姓名")) {
            hasCounterPartyName = true;
        }
        List<List<String>> rowList = parseFileToRowList(filePath, pdfPassword);
        parseListToBO(rowList, hxb, hasCounterPartyName);

        return hxb;
    }

    private void parseListToBO(List<List<String>> rowList, HXB hxb, boolean hasCounterPartyName) {
        List<HXBTran> hxbTrans = new ArrayList<>();
        hxb.setHxbTrans(hxbTrans);

        for (int i = 0; i < rowList.size(); i++) {
            List<String> rows = rowList.get(i);
//            System.out.println(StringUtils.join(rows));
            if (!rows.get(0).matches("\\d{4}-\\d{2}-\\d{2}")) {
                continue;
            }
            if (hasCounterPartyName) {
                if (rows.size() == 9) {
                    HXBTran hxbTran = new HXBTran();
                    hxbTran.setAccountingDate(rows.get(0));
                    hxbTran.setSummary(rows.get(1));
                    hxbTran.setTranAmt(rows.get(2));
                    hxbTran.setBalance(rows.get(3));
                    hxbTran.setTransactionUnit(rows.get(4));
                    hxbTran.setCounterPartyName(rows.get(5));
                    hxbTran.setCounterPartyNo(rows.get(6));
                    hxbTran.setCounterpartyBank(rows.get(7));
                    hxbTran.setRemarks(rows.get(8));
                    hxbTrans.add(hxbTran);

                } else if (rows.size() == 8 && rows.get(3).matches("\\d+(.\\d{1,2})?")) {
                    // 交易机构为空
                    //  [2025-03-27, E***入, 200.00, 313.26, , 张**, 59***4, 财***公司, 微***转账]
                    HXBTran hxbTran = new HXBTran();
                    hxbTran.setAccountingDate(rows.get(0));
                    hxbTran.setSummary(rows.get(1));
                    hxbTran.setTranAmt(rows.get(2));
                    hxbTran.setBalance(rows.get(3));
                    hxbTran.setTransactionUnit("");
                    hxbTran.setCounterPartyName(rows.get(4));
                    hxbTran.setCounterPartyNo(rows.get(5));
                    hxbTran.setCounterpartyBank(rows.get(6));
                    hxbTran.setRemarks(rows.get(7));
                    hxbTrans.add(hxbTran);

                }

            } else {
                // 交易机构为空
                //  [2025-03-27, E***入, 200.00, 313.26, , 微***账]
                if (rows.size() == 5 && rows.get(3).matches("\\d+(.\\d{1,2})?")) {
                    HXBTran hxbTran = new HXBTran();
                    hxbTran.setAccountingDate(rows.get(0));
                    hxbTran.setSummary(rows.get(1));
                    hxbTran.setTranAmt(rows.get(2));
                    hxbTran.setBalance(rows.get(3));
                    hxbTran.setTransactionUnit("");
                    hxbTran.setCounterPartyName("");
                    hxbTran.setCounterPartyNo("");
                    hxbTran.setCounterpartyBank("");
                    hxbTran.setRemarks(rows.get(4));
                    hxbTrans.add(hxbTran);
                } else if (rows.size() == 6) {
                    HXBTran hxbTran = new HXBTran();
                    hxbTran.setAccountingDate(rows.get(0));
                    hxbTran.setSummary(rows.get(1));
                    hxbTran.setTranAmt(rows.get(2));
                    hxbTran.setBalance(rows.get(3));
                    hxbTran.setTransactionUnit(rows.get(4));
                    hxbTran.setCounterPartyName("");
                    hxbTran.setCounterPartyNo("");
                    hxbTran.setCounterpartyBank("");
                    hxbTran.setRemarks(rows.get(5));
                    hxbTrans.add(hxbTran);
                }

            }

        }

    }

    private List<List<String>> parseFileToRowList(String filePath, String pdfPassword) {
        //页面所有table的记录，每一个元素代表下面一条记录cellList
        List<List<String>> rowList = new ArrayList<>();

        // 1. 读取文件
        File pdf = new File(filePath);

        // 2. pdfbox读取PDDocument
        try (PDDocument pdfDocument = PDDocument.load(pdf, pdfPassword)) {

            // 3. tabula新建ObjectExtractor和NurminenDetectionAlgorithm，同时准备接收表格Rectangle的结构
            ObjectExtractor objectExtractor = new ObjectExtractor(pdfDocument);
            // 优先使用SpreadsheetDetectionAlgorithm识别表格
            SpreadsheetDetectionAlgorithm detectionAlgorithm = new SpreadsheetDetectionAlgorithm();
            Map<Integer, List<Rectangle>> detectedTables = new HashMap<>();

            // 4. 获取每页的PageIterator
            PageIterator pages = objectExtractor.extract();

            // 5. 解析每页的Rectangle(table的位置)
            while (pages.hasNext()) {
                Page page = pages.next();
                List<Rectangle> tablesOnPage = detectionAlgorithm.detect(page);
                if (tablesOnPage.size() > 0) {
                    detectedTables.put(page.getPageNumber(), tablesOnPage);
                } else {
                    tablesOnPage = new NurminenDetectionAlgorithm().detect(page);
                    if (tablesOnPage.size() > 0) {
                        detectedTables.put(page.getPageNumber(), tablesOnPage);
                    }
                }

            }

            // 如果单页pdf中有多个表格，则解析出来的List<Rectangle>会有多个对象
            for (Map.Entry<Integer, List<Rectangle>> entry : detectedTables.entrySet()) {
                Page page = objectExtractor.extract(entry.getKey());
                // 默认每页只有一个表格，因此获取第0个rectangle/
                Rectangle rectangle = null; //NOSONAR
                List<Rectangle> rectangleList = entry.getValue();
                for (Rectangle r : rectangleList) {
                    double rectangleHeight = r.getHeight();
                    if (Double.compare(rectangleHeight, 0) > 0) {
                        rectangle = r;
                        break;
                    }
                }
                if (rectangle == null) {
                    throw new RuntimeException("rectangle is null.");
                }

                // 重新计算表格top
                float top = findTextBottom(filePath, pdfPassword, entry.getKey(), "Accounting Date");
                rectangle.setTop(top + 10);

                // 最后一页需要重新计算表格bottom
                if (entry.getKey() == pdfDocument.getNumberOfPages()) {
                    float bottom = findTextBottom(filePath, pdfPassword, pdfDocument.getNumberOfPages(), "1.请您妥善保管个人账户交易流水");
                    rectangle.setBottom(bottom - 20);
                } else {
                    rectangle.setBottom(rectangle.getBottom() + 900);
                }

                Page area = page.getArea(rectangle);

                // 6.通过table位置获取表格具体内容
                BasicExtractionAlgorithm bea = new BasicExtractionAlgorithm(area.getVerticalRulings());

                List<Table> tableList = null;
                try {
                    tableList = bea.extract(area);
                } catch (Exception e) {
                    log.info("rectangle extract table fail.");
                    continue;
                }
                List<List<Cell>> pageCellList = new ArrayList<>();
                // 如果每页有多个表格，解析每一个table
                for (Table table : tableList) {
                    for (int i = 0; i < table.getRowCount(); i++) {
                        List<Cell> cellList = new ArrayList<>();//一条记录，每个单元格占一个元素
                        for (int j = 1; j < table.getColCount(); j++) { //从第2列开始，第1列都是空的
                            RectangularTextContainer textContainer = table.getCell(i, j);
                            Cell cell = new Cell(textContainer.getText(false), textContainer.getRight(), textContainer.getBottom());
                            cellList.add(cell);
                        }
                        pageCellList.add(cellList);
                    }
                }
                rowList.addAll(mergeCells(pageCellList));
            }
            return rowList;
        } catch (
                Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static List<List<String>> mergeCells(List<List<Cell>> pageCellList) {
        List<List<String>> rowList = new ArrayList<>();

        for (int rowIndex = 0; rowIndex < pageCellList.size(); rowIndex++) {
            List<Cell> row = pageCellList.get(rowIndex);
            if (rowIndex == 0 || rowIndex == pageCellList.size() - 1) {
                List<String> rows = row.stream().map(cell -> cell.cellText).collect(Collectors.toList());
                rowList.add(rows);
                continue;
            }
            List<Cell> previousRow = pageCellList.get(rowIndex - 1);
            List<Cell> nextRow = pageCellList.get(rowIndex + 1);

            if (row.isEmpty() || StringUtils.isBlank(row.get(0).cellText)) {
                continue;
            }

            List<Cell> mergedRow = new ArrayList<>();

            for (int i = 0; i < row.size(); i++) {
                Cell currentCell = row.get(i);
                Cell previousCell = previousRow.get(i);
                float bottom = row.get(0).bottom;
                String previousToPreviousCellText = "";
                String previousCellText = "";

                // 如果存在上两行，且满足合并条件，则获取上两行单元格的内容
                if (rowIndex > 1) {
                    List<Cell> previousToPreviousRow = pageCellList.get(rowIndex - 2);
                    Cell previousToPreviousCell = previousToPreviousRow.get(i);
                    if (StringUtils.isBlank(previousToPreviousRow.get(0).cellText) && Math.abs(previousToPreviousCell.bottom - bottom) < MERGE_THRESHOLD) {
                        previousToPreviousCellText = previousToPreviousCell.cellText;
                    }
                }

                // 如果上一行满足合并条件，则将上一行单元格内容追加到当前单元格
                if (StringUtils.isBlank(previousRow.get(0).cellText) && Math.abs(previousCell.bottom - bottom) < MERGE_THRESHOLD) {
                    previousCellText = previousCell.cellText;
                }
                currentCell.cellText = previousToPreviousCellText + previousCellText + currentCell.cellText;
                Cell nextCell = nextRow.get(i);

                // 如果下一行满足合并条件，则将下一行单元格内容追加到当前单元格
                if (StringUtils.isBlank(nextRow.get(0).cellText) && Math.abs(nextCell.bottom - bottom) < MERGE_THRESHOLD) {
                    currentCell.cellText += nextCell.cellText;
                }

                // 如果存在下两行，且满足合并条件，则将下两行单元格内容追加到当前单元格
                if (rowIndex < pageCellList.size() - 2) {
                    List<Cell> nextToNextRow = pageCellList.get(rowIndex + 2);
                    Cell nextToNextCell = nextToNextRow.get(i);
                    if (StringUtils.isBlank(nextToNextRow.get(0).cellText) && Math.abs(nextToNextCell.bottom - bottom) < MERGE_THRESHOLD) {
                        currentCell.cellText += nextToNextCell.cellText;
                    }
                }

                mergedRow.add(currentCell);
            }

            List<String> stringRow = new ArrayList<>();
            for (Cell cell : mergedRow) {
                stringRow.add(cell.cellText);
            }
            rowList.add(stringRow);
        }

        return rowList;
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    static class Cell {
        String cellText;
        float right;
        float bottom;
    }

    private HXB parseHXBHeader(String pdfHeaderText) {
        HXB hxb = new HXB();

        String queryStartEndDate = "";
        if (pdfHeaderText.contains("查询起止日期：") && pdfHeaderText.contains("户名：")) {
            queryStartEndDate = pdfHeaderText.substring(pdfHeaderText.indexOf("查询起止日期：") + 7, pdfHeaderText.indexOf("户名：")).trim();
        }
        hxb.setQueryStartEndDate(queryStartEndDate);

        String accountName = "";
        if (pdfHeaderText.contains("户名：") && pdfHeaderText.contains("打印时间：")) {
            accountName = pdfHeaderText.substring(pdfHeaderText.indexOf("户名：") + 3, pdfHeaderText.indexOf("打印时间：")).trim();
        }
        hxb.setAccountName(accountName);

        String printTime = "";
        if (pdfHeaderText.contains("打印时间：") && pdfHeaderText.contains("Query Start&End")) {
            printTime = pdfHeaderText.substring(pdfHeaderText.indexOf("打印时间：") + 5, pdfHeaderText.indexOf("Query Start&End")).trim();
        }
        hxb.setPrintTime(printTime);

        String accountNumber = "";
        if (pdfHeaderText.contains("账号：") && pdfHeaderText.contains("卡号：")) {
            accountNumber = pdfHeaderText.substring(pdfHeaderText.indexOf("账号：") + 3, pdfHeaderText.indexOf("卡号：")).trim();
        }
        hxb.setAccountNumber(accountNumber);

        String cardNumber = "";
        if (pdfHeaderText.contains("卡号：") && pdfHeaderText.contains("流水编码：")) {
            cardNumber = pdfHeaderText.substring(pdfHeaderText.indexOf("卡号：") + 3, pdfHeaderText.indexOf("流水编码：")).trim();
        }
        hxb.setCardNumber(cardNumber);

        String transactionCode = "";
        if (pdfHeaderText.contains("流水编码：") && pdfHeaderText.contains("Account No.")) {
            transactionCode = pdfHeaderText.substring(pdfHeaderText.indexOf("流水编码：") + 5, pdfHeaderText.indexOf("Account No.")).trim();
        }
        hxb.setTransactionCode(transactionCode);

        String transactionType = "";
        if (pdfHeaderText.contains("交易类型：") && pdfHeaderText.contains("币种：")) {
            transactionType = pdfHeaderText.substring(pdfHeaderText.indexOf("交易类型：") + 5, pdfHeaderText.indexOf("币种：")).trim();
        }
        hxb.setTransactionType(transactionType);

        String currency = "";
        if (pdfHeaderText.contains("币种：") && pdfHeaderText.contains("金额区间：")) {
            currency = pdfHeaderText.substring(pdfHeaderText.indexOf("币种：") + 3, pdfHeaderText.indexOf("金额区间：")).trim();
        }
        hxb.setCurrency(currency);
        String amountRange = "";
        if (pdfHeaderText.contains("金额区间：") && pdfHeaderText.contains("Transaction Type Currency")) {
            amountRange = pdfHeaderText.substring(pdfHeaderText.indexOf("金额区间：") + 5, pdfHeaderText.indexOf("Transaction Type Currency")).trim();
        }
        hxb.setAmountRange(amountRange);
        String totalIncomeAmount = "";
        if (pdfHeaderText.contains("收入合计金额：") && pdfHeaderText.contains("支出合计金额：")) {
            totalIncomeAmount = pdfHeaderText.substring(pdfHeaderText.indexOf("收入合计金额：") + 7, pdfHeaderText.indexOf("支出合计金额：")).trim();
        }
        hxb.setTotalIncomeAmount(totalIncomeAmount);
        String totalExpenditureAmount = "";
        if (pdfHeaderText.contains("支出合计金额：") && pdfHeaderText.contains("交易合计笔数：")) {
            totalExpenditureAmount = pdfHeaderText.substring(pdfHeaderText.indexOf("支出合计金额：") + 7, pdfHeaderText.indexOf("交易合计笔数：")).trim();
        }
        hxb.setTotalExpenditureAmount(totalExpenditureAmount);

        String totalNumberOfTransactions = "";
        if (pdfHeaderText.contains("交易合计笔数：")) {
            totalNumberOfTransactions = pdfHeaderText.substring(pdfHeaderText.indexOf("交易合计笔数：") + 7, pdfHeaderText.indexOf("Total Income Amount Total")).trim();
        }
        hxb.setTotalNumberOfTransactions(totalNumberOfTransactions);

        return hxb;
    }

    static float findTextBottom(String pdfPath, String pwd, int pageIndex, String searchText) throws IOException {
        float[] y = new float[1];
        try (PDDocument document = PDDocument.load(new File(pdfPath), pwd)) {
            PDFTextStripper stripper = new PDFTextStripper() {
                @Override
                protected void writeString(String text, List<TextPosition> textPositions) {
                    StringBuilder sb = new StringBuilder();
                    for (TextPosition tp : textPositions) {
                        sb.append(tp.getUnicode());
                    }
                    if (sb.toString().contains(searchText)) {
                        TextPosition tp = textPositions.get(0);
//                        float x = tp.getX();
//                        float y = tp.getY();
                        y[0] = tp.getY();

                    }
                }
            };
            stripper.setSortByPosition(true);
            stripper.setStartPage(pageIndex);
            stripper.setEndPage(pageIndex);
            stripper.getText(document);
        }
        return y[0];
    }

    public static void main(String[] args) throws IOException {
        HXBPdfParser hxbPdfParser = new HXBPdfParser();
        String json = "";
        String path = "";
        json = hxbPdfParser.parseHXBPdfToJson("", "D:\\data\\file\\hxb\\华夏银行个人账户交易流水 -394282.pdf", "394282").getData();
        System.out.println(json);
//
//        path = "D:\\data\\file\\hxb\\华夏银行个人账户交易流水-215045-都不显示.pdf";
//        json = hxbPdfParser.parseHXBPdfToJson("", path, "215045").getData();
//        System.out.println(json);
//
//        path = "D:\\data\\file\\hxb\\华夏银行个人账户交易流水-319907.pdf";
//        json = hxbPdfParser.parseHXBPdfToJson("", path, "319907").getData();
//        System.out.println(json);
//
//        path = "D:\\data\\file\\hxb\\华夏银行个人账户交易流水-445529-不显示完整卡号.pdf";
//        json = hxbPdfParser.parseHXBPdfToJson("", path, "445529").getData();
//        System.out.println(json);
//
//        path = "D:\\data\\file\\hxb\\华夏银行个人账户交易流水-880107-不现实完整卡号.pdf";
//        json = hxbPdfParser.parseHXBPdfToJson("", path, "880107").getData();
//        System.out.println(json);
//
//        path = "D:\\data\\file\\hxb\\华夏银行个人账户交易流水-758846-不显示交易对手信息.pdf";
//        json = hxbPdfParser.parseHXBPdfToJson("", path, "758846").getData();
//        System.out.println(json);

        path = "D:\\data\\file\\hxb\\华夏银行个人账户交易流水.pdf";
        json = hxbPdfParser.parseHXBPdfToJson("", path, "").getData();
        System.out.println(json);
    }
}